import type { AxiosResponse } from "axios"
import { uploadFileReq } from "@/common/request"
import { storePrefix, rsa_privateKey, rsa_publicKey, tokenKey, aes_iv, aes_key, md5_key, layoutConfig } from "@/common/config"
import { hasAuth } from "@/common/addGlobalProperty"
import type { Ref } from "vue"
import { h, nextTick } from "vue"
import JSEncrypt from "jsencrypt"
import md5 from "blueimp-md5"
import CryptoJS from "crypto-js";


const setToken = (token: string) => {
   bs.setItem(tokenKey, { token })
}



const getToken = (): string => {
   const obj = bs.getItem(tokenKey)
   if (obj) return obj.token
   return ""
}


const deleteToken = () => {
   bs.removeItem(tokenKey)
}


const get = (obj: ObjType, key: string) => {
   let res: any
   key.split('.').forEach((k, i) => { if (i == 0) res = obj[k]; else res = res[k] })
   return res
}

const downloadFileByBlob = (res: AxiosResponse, blobKey: string = 'data', name?: string) => {
   if (!(res.data && res.data instanceof Blob)) return
   const content = res.headers['content-disposition'];
   //console.log(content)
   const filename = content ? decodeURIComponent(content.split('=')[1]) : '未知文件'
   const a = document.createElement('a');
   a.href = window.URL.createObjectURL(get(res, blobKey))
   a.download = name ? name : filename;
   a.click();
   setTimeout(() => { window.URL.revokeObjectURL(a.href) }, 1)
   return true
}


const printFileByBlob = (blob: any) => {
   const id = "blob-print-iframe"
   document.getElementById(id)?.remove()
   const printIframe = document.createElement('iframe')
   printIframe.id = id
   printIframe.style.display = 'none'
   printIframe.src = URL.createObjectURL(blob)
   document.body.appendChild(printIframe)
   setTimeout(() => {
      printIframe.contentWindow?.print()
      URL.revokeObjectURL(printIframe.src)
   }, 100)
}


const fileStreamToUrl = (res: ObjType, streamKey: string = 'data') => {
   const fileType = res.headers['content-type']
   const blob = new Blob([get(res, streamKey)], {
      type: fileType,
   })
   const url = window.URL.createObjectURL(blob)
   return {
      url,
      releaseUrl: window.URL.revokeObjectURL(url)
   }
}


const getBase64 = (blob: Blob) => {
   return new Promise((rel, rej) => {
      const reader = new FileReader();
      reader.onload = e => { rel(e.target?.result) }
      reader.onerror = e => { rej(e) };
      reader.readAsDataURL(blob);
   })
}


const base64ToBlob = (base64String: string, mimeType = 'application/octet-stream') => {
   const byteChars = window.atob(base64String);
   const byteArrays = [];

   // 将每6个字符（3字节）转化为16进制数
   for (let offset = 0; offset < byteChars.length; offset += 64) { // 这里使用64作为分块大小，可以根据需要调整
      const chunk = byteChars.slice(offset, offset + 64);

      let byteNumbers = new Array(chunk.length);
      for (let i = 0; i < chunk.length; i++) {
         byteNumbers[i] = chunk.charCodeAt(i);
      }

      const byteArray = new Uint8Array(byteNumbers);
      byteArrays.push(byteArray);
   }

   const blob = new Blob(byteArrays, { type: mimeType });
   return blob;
}


const downloadImageByBase64 = (base64: string, filename: string) => {
   const aLink = document.createElement('a');
   aLink.href = base64;
   aLink.download = filename;
   document.body.appendChild(aLink);
   aLink.click();
   document.body.removeChild(aLink);
}


const uploadFile = async (file: Blob | Blob[], fieldName: string = 'file', url?: string, config?: ObjType) => {
   const formData = new FormData()
   let reqUrl: string
   if (!Array.isArray(file)) {
      reqUrl = "/uploadFile"
      if (url) reqUrl = url
      formData.append(fieldName, file)
   } else {
      reqUrl = "/uploadFiles"
      if (url) reqUrl = url
      for (let fileObj of file) {
         formData.append(fieldName, fileObj)
      }
   }
   if (config) Object.entries(config).forEach(([k, v]) => { formData.append(k, v) })
   return uploadFileReq(reqUrl, formData)
}


const listToTree = <nodeT = ObjType>(list: nodeT[], idKeyName: string, PidKeyName: string, cKeyName = 'children', isWellDeepClone = false, callback?: (node: nodeT) => void,
   callback_findC?: (node: nodeT) => void) => {
   let _list
   _list = deepClone(list, isWellDeepClone)
   let res
   for (let i of _list) {
      for (let j of _list) {
         if (!i[PidKeyName]) res = i
         if (i[idKeyName] == j[PidKeyName]) {
            if (!i[cKeyName]) i[cKeyName] = []
            i[cKeyName].push(j)
            callback_findC && callback_findC(i)
         }
      }
      callback && callback(i)
   }
   return res
}



const traverseTreeArr = (treeArr: TreeType[], cKeyName = 'children', callback?: (node: ObjType) => void, idKeyName?: string, targetId?: string | number): ObjType | null => {
   let target: ObjType | null = null
   for (const v of treeArr) {
      if (targetId && idKeyName && v[idKeyName] == targetId) {    // 传了targetId和就是寻找节点
         callback && callback(v)
         target = v
         return target
      }
      !targetId && callback && callback(v)     // 没传就正常遍历
      if (v[cKeyName]) target = traverseTreeArr(v[cKeyName], cKeyName, callback, idKeyName, targetId)
   }
   return target
}



const treeArrToList = (treeArr: TreeType[], cKeyName = 'children', isWellDeepClone = false) => {
   const list: TreeType[] = []
   let treeList
   treeList = deepClone(treeArr, isWellDeepClone)
   traverseTreeArr(treeList, cKeyName, (node) => { list.push(node) },)
   return list
}



const sortManyRuleArr = (arr: ObjType[], sortKeys: string[], way: 'up' | 'down' = 'up', isWellDeepClone = false) => {
   const _arr = deepClone<ObjType[]>(arr, isWellDeepClone)
   const res: ObjType[][] = []
   sortKeys.forEach((sortKey, i) => {
      res[i] = []
      _arr.forEach(v => {
         if (Object.keys(v).includes(sortKey) && !v.del) {
            res[i].push(v)
            v.del = 1
         }
      })
   })
   res.forEach((v, i) => {
      const k = sortKeys[i]
      v.sort((a, b) => {
         let pre = get(a, k), next = get(b, k)
         if (typeof pre != 'number') pre = 999999
         if (typeof next != 'number') next = 999999
         if (way == 'up') return pre - next
         else return next - pre
      })
   })
   if (way == 'up') return res.flat()
   else return res.reverse().flat()
}


const sortArr = (arr: ObjType[], sortKey: string, way: 'up' | 'down' = 'up', changeOrigin = true, isWellDeepClone = false) => {
   const _arr: ObjType[] = changeOrigin ? arr : deepClone(arr, isWellDeepClone);
   _arr.sort((pre, next) => {
      let preR = get(pre, sortKey), nextR = get(next, sortKey)
      let preT = pre.createTime ? pre.createTime : 0
      let nextT = next.createTime ? next.createTime : 0
      if (!(typeof preR == "number" && !isNaN(preR))) preR = 999 + preT;
      if (!(typeof nextR == "number" && !isNaN(nextR))) nextR = 999 + nextT;
      if (way == 'up') return preR - nextR;
      else return nextR - preR
   });
   return _arr;
}

const sortTreeArr = <treeT = any>(treeArr: TreeType[], sortKey: string, way: 'up' | 'down' = 'up', changeOrigin = true, isWellDeepClone = false) => {
   const res = sortArr(treeArr, sortKey, way, changeOrigin, isWellDeepClone);
   res.forEach((v) => {
      if (Array.isArray(v.children)) v.children = sortTreeArr(v.children, sortKey);
   });
   return res as treeT[];
}


const deepClone = <T = any>(obj: any, isWellDeepClone = true): T => {
   if (!isWellDeepClone) {
      try {
         const res = JSON.parse(JSON.stringify(obj))
         return res
      } catch (error) {
         return deepClone(obj)
      }
   }
   if (!Array.isArray(obj) && Object.prototype.toString.call(obj) !== '[object Object]') {
      return obj
   }
   const cloneobj: ObjAndArrType = Array.isArray(obj) ? [] : {}
   for (const [k, v] of Object.entries(obj)) {
      if (Array.isArray(v) || Object.prototype.toString.call(v) === '[object Object]')
         cloneobj[k] = deepClone(v)
      else cloneobj[k] = v
   }
   return cloneobj as T
}


const AesEncrypt = (text: string) => {
   return CryptoJS.AES.encrypt(text, aes_key).toString()
}


const AesDecrypt = (text: string) => {
   return CryptoJS.AES.decrypt(text, aes_key).toString(CryptoJS.enc.Utf8)
}


const AesEncryptHex = (text: string) => {
   const secretKey = CryptoJS.enc.Hex.parse(aes_key);
   const initVector = CryptoJS.enc.Hex.parse(aes_iv);
   const encrypted = CryptoJS.AES.encrypt(
      CryptoJS.enc.Utf8.parse(text),
      secretKey,
      { iv: initVector, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
   );

   return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}


const AesDecryptHex = (text: string) => {
   const secretKey = CryptoJS.enc.Hex.parse(aes_key);
   const initVector = CryptoJS.enc.Hex.parse(aes_iv);
   const ciphertext = CryptoJS.enc.Base64.parse(text);
   const decrypted = CryptoJS.AES.decrypt(
      { ciphertext: ciphertext },
      secretKey,
      { iv: initVector, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
   );
   return decrypted.toString(CryptoJS.enc.Utf8);
}


const AesEncryptUtf = (text: string) => {
   const key = CryptoJS.enc.Utf8.parse(aes_key);
   const srcText = CryptoJS.enc.Utf8.parse(text);
   const encrypted = CryptoJS.AES.encrypt(srcText, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
   return encrypted.toString();
}


const AesDecryptUtf = (text: string) => {
   const key = CryptoJS.enc.Utf8.parse(aes_key);
   const decrypt = CryptoJS.AES.decrypt(text, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
   return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}



const RSAEncrypt = (text: string) => {
   const encryptor = new JSEncrypt();
   encryptor.setPublicKey(rsa_publicKey);
   return encryptor.encrypt(text);
}


const RSAdecrypt = (text: string) => {
   const encryptor = new JSEncrypt();
   encryptor.setPrivateKey(rsa_privateKey);
   return encryptor.decrypt(text);
}


const md5Encrypt = (text: string) => {
   return md5(text, md5_key)
}


const getLayoutContentHeight = () => {
   const selector = '.BasicLayout .layout-rigth-main .content'
   const content = document.querySelector(selector)
   const { gap } = layoutConfig
   if (content) return parseInt(getComputedStyle(content as Element, null).minHeight) - parseInt(gap) * 2
   else console.error((`未获取到布局内容区元素，请在组件的挂载钩子调用getLayoutContentHeight方法并且检查选择器名称（${selector}）是否正确！`))
   return 0
}


export type setTableHeightParamsType = {
   tableHeightRef: Ref
   otherSectionHeight?: number
   tToolHeight?: number
   paginationHeight?: number
   isTable?: boolean,
   isHandleTableDomHeight?: boolean
}

const setTableHeight = (config: setTableHeightParamsType) => {
   const { tableHeightRef, otherSectionHeight = 0, tToolHeight = 52, paginationHeight = 44, isTable = true, isHandleTableDomHeight = true } = config
   let h: number | undefined
   if (window.innerHeight < 680) {
      tableHeightRef.value = ''    // 当maxHeight值为undefined时，不执行高度重新赋值,''可以
      h = undefined
   } else {
      const contentLayoutHeight = getLayoutContentHeight()
      if (contentLayoutHeight) {
         const tableDomMaxH = getLayoutContentHeight() - (otherSectionHeight + tToolHeight + paginationHeight)
         if (isTable) h = tableDomMaxH
         else h = getLayoutContentHeight() - otherSectionHeight
         tableHeightRef.value = h
         if (isTable && isHandleTableDomHeight) {
            nextTick(() => {
               const tableDom: HTMLElement | null = document.querySelector(".el-table")
               if (tableDom) {
                  tableDom.style.height = tableDomMaxH + 'px'
               }
            })
         }
      } else h = undefined
   }
   //console.log(h)
   return h
}


const getDounceFunc = (callback: () => void, timeout = 1500, immediate = true) => {
   let debounceTimer: any = 0
   return () => {
      if (!debounceTimer) {
         if (immediate) { callback(); debounceTimer = setTimeout(() => { debounceTimer = 0 }, timeout) }
         else { debounceTimer = setTimeout(() => { callback(); debounceTimer = 0 }, timeout) }
      }
      else {
         clearTimeout(debounceTimer)
         debounceTimer = setTimeout(() => {
            if (!immediate) callback()
            debounceTimer = 0
         }, timeout)
      }
   }
}


const getThrottleFunc = (callback: () => void, timeout = 1500, immediate = true) => {
   let throttleTimer: any = 0
   return () => {
      if (!throttleTimer) {
         if (immediate) {
            callback()
            throttleTimer = setTimeout(() => { throttleTimer = 0 }, timeout)
         }
         else {
            throttleTimer = setTimeout(() => { callback(); throttleTimer = 0 }, timeout)
         }
      }
   }
}

const setFixedColumnWidth = (fixedColumnActionKeys: (string[] | string)[], columns: Ref<TableColConfig[]>, noNeedAuthBtnNum = 0, btnW = 48) => {
   let btnNum = 0, w = 0
   fixedColumnActionKeys.forEach(v => {
      if (Array.isArray(v)) {
         if (hasAuth(v)) btnNum++
      }
      else {
         if (v.includes("@@")) {
            if (hasAuth([v.split("@@")[1]])) btnNum += parseFloat(v.split("@@")[0])
         }
         else {
            if (hasAuth([v])) btnNum++
         }
      }
   })
   btnNum += noNeedAuthBtnNum
   if (btnNum) w = btnNum * btnW + Math.ceil((btnNum - 1)) * 12 + 14 * 2  // 12是按钮之间的间距，14是两边的留白
   else w = 0
   columns.value.forEach(v => {
      if (v.label == '操作') {
         if (w) v.width = w
         else {
            v.width = 1      // 对于el-table的固定列宽，不能给0或者0px，给了不生效
            v.label = ""
         }
      }
   })
   if (w) return w
   else return 1
}


type HRenderTextStatus = 'danger' | 'success'

const getStatusTextByHRender = (text: string, status: HRenderTextStatus) => {
   const statusToColorMap: Record<HRenderTextStatus, string> = {
      danger: 'rgb(245, 108, 108)',
      success: 'rgb(29, 165, 122)',
   }
   return h('span', { style: { color: statusToColorMap[status] } }, text)
}

/**
 * @description 时间格式化
 * @remark 用Y代表年份，M代表月份，D代表日，h代表时，m代表分，s代表秒。传入年份是4位，其他如果传入，则必须穿传2位。
 * @param d 日期对象或时间戳
 * @param format 格式化的模板，默认为 'YYYY/MM/DD hh:mm:ss'
 * @returns 
 */
const formatDate = (d: number | Date, format = 'YYYY/MM/DD hh:mm:ss') => {
   //使用规则：
   for (const v of format) { if (!'YMDhms'.includes(v)) throw new Error('请传入正确的时间模板') }
   if (
      (format.includes('Y') && format.match(/Y/g)!.length !== 4) ||
      (format.includes('M') && format.match(/M/g)!.length !== 2) ||
      (format.includes('D') && format.match(/D/g)!.length !== 2) ||
      (format.includes('h') && format.match(/h/g)!.length !== 2) ||
      (format.includes('m') && format.match(/m/g)!.length !== 2) ||
      (format.includes('s') && format.match(/s/g)!.length !== 2)
   )
      throw new Error('请传入正确的时间模板')

   const date = new Date(d)

   const config: any = {
      YYYY: date.getFullYear(),
      MM: (date.getMonth() + 1) >= 10 ? date.getMonth() + 1 : '0' + date.getMonth() + 1,
      DD: date.getDate() >= 10 ? date.getDate() : "0" + date.getDate(),
      hh: date.getHours() >= 10 ? date.getHours() : "0" + date.getHours(),
      mm: date.getMinutes() >= 10 ? date.getMinutes() : "0" + date.getMinutes(),
      ss: date.getSeconds() >= 10 ? date.getSeconds() : "0" + date.getSeconds()
   }
   for (const k in config) { format = format.replace(k, config[k as any]) }
   return format
}

export type bsType = {
   getItem: (key: string) => any
   /** expire：过期时间，单位ms*/
   setItem: (key: string, value: any, expire?: number) => void
   removeItem: (key: string) => void,
   clear: () => void
   [key: string | symbol]: any
}

const creatBs = (config?: { prefix?: string, isEncrypt?: boolean, storeWay?: 'localStorage' | 'sessionStorage', }
): bsType => {
   const {
      prefix = storePrefix,
      isEncrypt = import.meta.env.VITE_NODE_ENV != 'dev',
      // isEncrypt=true,
      storeWay = 'localStorage',
   } = config ? config : {}

   const configKey = Symbol("config")

   const encryptWays = {
      url: [encodeURIComponent, decodeURIComponent],
      aes: [AesEncrypt, AesDecrypt]
   }

   const [iEncrypt, iDecrypt] = encryptWays.aes

   return {
      [configKey]: { prefix, isEncrypt, storeWay: storeWay === 'localStorage' ? localStorage : sessionStorage },
      getItem(key: string) {
         let obj = this[configKey].storeWay.getItem(`${this[configKey].prefix}_${key}`) as any
         if (!obj) return null
         if (this[configKey].isEncrypt)
            obj = iDecrypt(obj)
         try {
            obj = JSON.parse(obj)
         } catch (error) {
            if (import.meta.env.VITE_NODE_ENV == 'dev') console.log(`数据损坏 ${key}`)
            this.removeItem(key)
            return null
         }
         if (obj.expire && Date.now() >= obj.expire) {
            if (import.meta.env.VITE_NODE_ENV == 'dev') {
               console.log(`存储到期的时间:${new Date(obj.expire).toLocaleString()}   ${key}`)
               console.log(`目前的时间:${new Date().toLocaleString()}   ${key}`)
            }
            this.removeItem(key)
            return null
         }

         return obj.value ? obj.value : null
      },
      setItem(key: string, value: any, expire = 0) {
         let storeValue = JSON.stringify({
            value,
            expire: expire ? Date.now() + expire : 0,
         })
         if (this[configKey].isEncrypt) storeValue = iEncrypt(storeValue)
         // 默认不过期，单位ms
         this[configKey].storeWay.setItem(`${this[configKey].prefix}_${key}`, storeValue)
      },
      removeItem(key: string) {
         this[configKey].storeWay.removeItem(`${this[configKey].prefix}_${key}`)
      },
      clear() {
         this[configKey].storeWay.clear()
      }
   }
}
const bs = creatBs()


export {
   creatBs,
   bs,
   get,
   setToken,
   getToken,
   deleteToken,
   downloadFileByBlob,
   fileStreamToUrl,
   getBase64,
   base64ToBlob,
   downloadImageByBase64,
   printFileByBlob,
   uploadFile,
   listToTree,
   treeArrToList,
   traverseTreeArr,
   sortArr,
   sortTreeArr,
   sortManyRuleArr,
   deepClone,
   AesEncrypt,
   AesDecrypt,
   AesEncryptHex,
   AesDecryptHex,
   AesEncryptUtf,
   AesDecryptUtf,
   RSAEncrypt,
   RSAdecrypt,
   md5Encrypt,
   getLayoutContentHeight,
   setTableHeight,
   getDounceFunc,
   getThrottleFunc,
   setFixedColumnWidth,
   getStatusTextByHRender,
   formatDate,
}